package com.leyou.search.service.Impl;

import com.leyou.common.exception.LyException;

import com.leyou.item.client.ItemFeignClient;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.leyou.search.vo.GoodsRequestBodyVo;
import com.leyou.starter.elastic.entity.PageInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.stream.Collectors;

import static com.leyou.search.constants.SearchConstants.*;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private GoodsRepository repository;

    @Autowired
    private ItemFeignClient itemFeignClient;


    // 自动补全的自动名
    @Override
    public Mono<List<String>> querySuggestion(String key) {
        return repository.suggestBySingleField(SUGGESTION_FIELD, key);
    }

    //分页搜索商品数据
    @Override
    public Mono<PageInfo<Goods>> queryPageInfoByGoods(GoodsRequestBodyVo goodsRequestBodyVo) {

        //构建条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //调用getQueryBuilder获取分词匹配查询方法
        sourceBuilder.query(getQueryBuilder(goodsRequestBodyVo));

        //构建分页查询条件,考虑到查询的商品可能没有值
        Integer page = goodsRequestBodyVo.getPage() == null ? 0 : (goodsRequestBodyVo.getPage() - 1);
        sourceBuilder.from(page);
        sourceBuilder.size(goodsRequestBodyVo.getSIZE());

        //构建高亮查询条件
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<am>");
        highlightBuilder.postTags("</am>");
        sourceBuilder.highlighter(highlightBuilder);

        //构建排序条件
        Set<String> soreBySet = new HashSet<>();
        soreBySet.add("sold");//销量排序
        soreBySet.add("prices");//价格排序
        soreBySet.add("updateTime");//(新品)时间排序
        //获取排序，判断是否有排序条件传入，并且在销量，价格，新品之内
        String sortBy = goodsRequestBodyVo.getSortBy();
        if (StringUtils.isNotEmpty(sortBy) && soreBySet.contains(sortBy)) {
            //获取排序方式如果是true就是降序否则就是升序
            sourceBuilder.sort(SortBuilders.fieldSort(sortBy)
                    .order(goodsRequestBodyVo.getDesc() ? SortOrder.DESC : SortOrder.ASC));
        }

        return repository.queryBySourceBuilderForPageHighlight(sourceBuilder);
    }



    //根据商品数据，构建页面搜索条件
    @Override
    public Mono<Map<String, List<?>>> queryAggregationByGoods(GoodsRequestBodyVo goodsRequestBodyVo) {
        //构建条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //聚合查询是构建在已经查询出来的结果上，所以先要有分词匹配查询
        sourceBuilder.query(getQueryBuilder(goodsRequestBodyVo));
        //要查询但是不展示结果
        sourceBuilder.size(0);
        //构建聚合查询
        sourceBuilder.aggregation(AggregationBuilders.terms("categoryAgg").field("categoryId").size(30));//分类id聚合
        sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field("brandId").size(30));//品牌id聚合
        //规格参数聚合
        sourceBuilder.aggregation(AggregationBuilders.nested("specsAgg", "specs")//规格参数总聚合
                .subAggregation(AggregationBuilders.terms("nameAgg").field("specs.name").size(30)//规格名称总聚合
                        .subAggregation(AggregationBuilders.terms("valueAgg").field("specs.value")).size(30)));//规格名称下的值总聚合

        //根据查询结果构建聚合
        Mono<Aggregations> aggregationsMono = repository.aggregationBySourceBuilder(sourceBuilder);
        //将查询结果进行再一次的封装，返回需要的Map类型
        //Mono中mao方法对每个元素进行挨个运算
        return aggregationsMono.map(aggregations ->
                {
                    Map<String, List<?>> resultMap = new LinkedHashMap<>();

                    //获取聚合查询的分类id结果对象 --> 向下转型才有getBuckets方法，才能获取到分类id桶
                    Terms categoryAgg = aggregations.get("categoryAgg");

                    //分类id桶,遍历转型为Long类型的分类id集合
                    List<Long> cidList = categoryAgg.getBuckets()
                            .stream()
                            .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber())
                            .map(Number::longValue)
                            .collect(Collectors.toList());

                    if (!CollectionUtils.isEmpty(cidList)) {
                        resultMap.put("分类", this.itemFeignClient.queryCategoriesByIds(cidList));
                    }

                    //获取聚合查询的品牌id结果对象
                    Terms brandAgg = aggregations.get("brandAgg");
                    List<Long> bidList = brandAgg.getBuckets()
                            .stream()
                            .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber())
                            .map(Number::longValue)
                            .collect(Collectors.toList());

                    if (!CollectionUtils.isEmpty(bidList)) {
                        resultMap.put("品牌", itemFeignClient.queryBrandsByIds(bidList));
                    }


                    //获取聚合规格参数结果对象
                    Nested specsAgg = aggregations.get("specsAgg");

                    //获取聚合规格名称结果对象
                    Terms nameAgg = specsAgg.getAggregations().get("nameAgg");

                    //获取聚合规格名称key
                    nameAgg.getBuckets().forEach(bucket ->{
                        String resultKey = ((Terms.Bucket) bucket).getKeyAsString();

                        //根据name聚合名称获取value聚合结果对象
                        Terms valueAgg = ((Terms.Bucket) bucket).getAggregations().get("valueAgg");

                        //从value聚合结果对象获取结果集合
                        List<String> resultValueList = valueAgg.getBuckets()
                                .stream()
                                .map(valueBucket -> ((Terms.Bucket) valueBucket).getKeyAsString())
                                .collect(Collectors.toList());

                        resultMap.put(resultKey, resultValueList);
                    });

                    return resultMap;
                }
         );
    }





    //获取分词匹配查询方法
    private QueryBuilder getQueryBuilder(GoodsRequestBodyVo goodsRequestBodyVo) {
        if (StringUtils.isBlank(goodsRequestBodyVo.getKey())) {
            throw new LyException("搜索关键字为空", 400);
        }

        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        //给bool添加查询条件
        boolQueryBuilder.must(//根据搜索关键字，构建分词匹配查询
                QueryBuilders.matchQuery("title", goodsRequestBodyVo.getKey()).operator(Operator.AND));

        //如果过滤条件不为空，那么给添加bool查询添加条件
        if (!CollectionUtils.isEmpty(goodsRequestBodyVo.getFilters())) {
            //获取map中的key，value
            for (Map.Entry<String, String> FilterEntry : goodsRequestBodyVo.getFilters().entrySet()) {

                //过滤条件key值
                String boolQueryKey = FilterEntry.getKey();

                //过滤条件value值
                String boolQueryValue = FilterEntry.getValue();

                //key替换为brandId或者categoryId
                if ("品牌".equals(boolQueryKey)) {
                    boolQueryBuilder.filter(QueryBuilders.termQuery("brandId", boolQueryValue));
                } else if ("分类".equals(boolQueryKey)) {
                    boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId", boolQueryValue));
                } else {
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    subBoolQuery.must(QueryBuilders.termQuery("specs.name", boolQueryKey));
                    subBoolQuery.must(QueryBuilders.termQuery("specs.value", boolQueryValue));

                    boolQueryBuilder.filter(QueryBuilders.nestedQuery("specs", subBoolQuery, ScoreMode.None));
                }
            }
        }
        return boolQueryBuilder;
    }
}
